/*
* Copyright (c) 2024 China Unicom Digital Technology Co., Ltd.
* openFuyao is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* Author: YuXiang Guo
* Date: 2025-08-18
 */

package metric

import (
	"testing"
	"time"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// TestPodStorage
func TestPodStorage(t *testing.T) {
	tests := []struct {
		name           string
		setup          func(*podStorage)
		storeBatch     *MetricsBatch
		getPods        []*metav1.PartialObjectMetadata
		expectedError  bool
		expectedResult *NodeUsageReport
	}{
		{
			name:          "empty storage get metrics",
			setup:         func(s *podStorage) {},
			getPods:       []*metav1.PartialObjectMetadata{},
			expectedError: false,
			expectedResult: &NodeUsageReport{
				Node:               "test-node",
				Pods:               make(map[PodRef]corev1.ResourceList),
				Containers:         make(map[PodRef]map[string]corev1.ResourceList),
				PodsFromContainers: make(map[PodRef]corev1.ResourceList),
			},
		},
		{
			name: "get metrics for non-existent pod",
			setup: func(s *podStorage) {
				// 存储一些数据但不包含请求的pod
				s.last[NewPodRef("default", "existing-pod")] = ContanersMetricsPoint{
					Containers: map[string]MetricsPoint{
						"container1": {Timestamp: time.Now(), CumulativeCpuUsed: 1000},
					},
				}
			},
			getPods: []*metav1.PartialObjectMetadata{
				{
					TypeMeta: metav1.TypeMeta{
						APIVersion: "v1",
						Kind:       "Pod",
					},
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "default",
						Name:      "non-existent-pod",
					},
				},
			},
			expectedError: false,
			expectedResult: &NodeUsageReport{
				Node:               "test-node",
				Pods:               make(map[PodRef]corev1.ResourceList),
				Containers:         make(map[PodRef]map[string]corev1.ResourceList),
				PodsFromContainers: make(map[PodRef]corev1.ResourceList),
			},
		},
		{
			name: "store and get valid metrics",
			setup: func(s *podStorage) {
				// 设置初始数据
				now := time.Now()
				s.last[NewPodRef("default", "test-pod")] = ContanersMetricsPoint{
					Containers: map[string]MetricsPoint{
						"container1": {
							StartTime:         now.Add(-time.Minute),
							Timestamp:         now.Add(-30 * time.Second),
							CumulativeCpuUsed: 500,
						},
					},
				}
				s.prev[NewPodRef("default", "test-pod")] = ContanersMetricsPoint{
					Containers: map[string]MetricsPoint{
						"container1": {
							StartTime:         now.Add(-time.Minute),
							Timestamp:         now.Add(-40 * time.Second),
							CumulativeCpuUsed: 200,
						},
					},
				}
			},

			getPods: []*metav1.PartialObjectMetadata{
				{
					TypeMeta: metav1.TypeMeta{
						APIVersion: "v1",
						Kind:       "Pod",
					},
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "default",
						Name:      "test-pod",
					},
				},
			},
			expectedError: false,
			expectedResult: &NodeUsageReport{
				Node: "test-node",
				Pods: make(map[PodRef]corev1.ResourceList),
				Containers: map[PodRef]map[string]corev1.ResourceList{
					NewPodRef("default", "test-pod"): {
						"container1": corev1.ResourceList{
							corev1.ResourceCPU: *resource.NewMilliQuantity(30000, resource.DecimalSI), // 300ms over 10s = 30m/s
						},
					},
				},
				PodsFromContainers: map[PodRef]corev1.ResourceList{
					NewPodRef("default", "test-pod"): {
						corev1.ResourceCPU: *resource.NewMilliQuantity(30000, resource.DecimalSI),
					},
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			storage, ok := NewPodStorage("test-node", time.Minute).(*podStorage)
			if !ok {
				t.Errorf("failed to type assert to *podStorage: got types %T", NewPodStorage("test-node", time.Minute))
			}
			tt.setup(storage)

			result, err := storage.GetLastMetrics(tt.getPods...)

			if tt.expectedError && err == nil {
				t.Error("Expected error but got none")
			}
			if !tt.expectedError && err != nil {
				t.Errorf("Unexpected error: %v", err)
			}

			// 验证结果结构
			if result != nil && tt.expectedResult != nil {
				if result.Node != tt.expectedResult.Node {
					t.Errorf("Expected node %s, got %s", tt.expectedResult.Node, result.Node)
				}

				// 验证容器数量
				if len(result.Containers) != len(tt.expectedResult.Containers) {
					t.Errorf("Expected %d containers, got %d", len(tt.expectedResult.Containers), len(result.Containers))
				}

				// 验证pod数量
				if len(result.PodsFromContainers) != len(tt.expectedResult.PodsFromContainers) {
					t.Errorf("Expected %d pods, got %d", len(tt.expectedResult.PodsFromContainers), len(result.PodsFromContainers))
				}
			}

			t.Logf("Test passed")
		})
	}
}

// TestStoreMetrics
func TestStoreMetrics(t *testing.T) {
	storage, ok := NewPodStorage("test-node", time.Minute).(*podStorage)
	if !ok {
		t.Errorf("failed to type assert to *podStorage: got types %T", NewPodStorage("test-node", time.Minute))
	}
	now := time.Now()
	testBatch := &MetricsBatch{
		Pods: map[PodRef]MetricsPoint{
			NewPodRef("default", "pod1"): {
				StartTime:         now.Add(-time.Minute),
				Timestamp:         now,
				CumulativeCpuUsed: 1000,
			},
		},
		Containers: map[PodRef]ContanersMetricsPoint{
			NewPodRef("default", "pod1"): {
				Containers: map[string]MetricsPoint{
					"container1": {
						StartTime:         now.Add(-time.Minute),
						Timestamp:         now,
						CumulativeCpuUsed: 1000,
					},
				},
			},
		},
	}

	storage.Store(testBatch)

	storage.mu.RLock()
	defer storage.mu.RUnlock()

	if len(storage.last) != 1 {
		t.Errorf("Expected 1 pod in last storage, got %d", len(storage.last))
	}

	if len(storage.prev) != 0 {
		t.Errorf("Expected 0 pod in prev storage, got %d", len(storage.prev))
	}
}

// TestIsValidPoint
func TestIsValidPoint(t *testing.T) {
	tests := []struct {
		name          string
		point         MetricsPoint
		period        time.Duration
		minResolution time.Duration
		expected      bool
	}{
		{
			name: "valid point",
			point: MetricsPoint{
				StartTime: time.Now().Add(-time.Minute),
				Timestamp: time.Now().Add(-30 * time.Second),
			},
			period:        time.Minute,
			minResolution: time.Second,
			expected:      true,
		},
		{
			name: "start time after timestamp",
			point: MetricsPoint{
				StartTime: time.Now().Add(30 * time.Second),
				Timestamp: time.Now(),
			},
			period:        time.Minute,
			minResolution: time.Second,
			expected:      false,
		},
		{
			name: "duration too long",
			point: MetricsPoint{
				StartTime: time.Now().Add(-2 * time.Minute),
				Timestamp: time.Now(),
			},
			period:        time.Minute,
			minResolution: time.Second,
			expected:      false,
		},
		{
			name: "duration too short",
			point: MetricsPoint{
				StartTime: time.Now().Add(-500 * time.Millisecond),
				Timestamp: time.Now(),
			},
			period:        time.Minute,
			minResolution: time.Second,
			expected:      false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isValidPoint(tt.point, tt.period, tt.minResolution)
			if result != tt.expected {
				t.Errorf("isValidPoint() = %v, expected %v ", result, tt.expected)
			}
		})
	}
}

// TestHandlePoint
func TestHandlePoint(t *testing.T) {
	storage, ok := NewPodStorage("test-node", time.Minute).(*podStorage)
	if !ok {
		t.Errorf("failed to type assert to *podStorage: got types %T", NewPodStorage("test-node", time.Minute))
	}
	now := time.Now()

	tests := []struct {
		name          string
		setup         func()
		podRef        PodRef
		containerName string
		newPoint      MetricsPoint
		expected      bool
	}{
		{
			name: "valid new point",
			setup: func() {
				// 清空存储
				storage.last = make(map[PodRef]ContanersMetricsPoint)
				storage.prev = make(map[PodRef]ContanersMetricsPoint)
			},
			podRef:        NewPodRef("default", "pod1"),
			containerName: "container1",
			newPoint: MetricsPoint{
				StartTime:         now.Add(-time.Minute),
				Timestamp:         now.Add(-30 * time.Second),
				CumulativeCpuUsed: 1000,
			},
			expected: true,
		},
		{
			name: "invalid point with no existing data",
			setup: func() {
				storage.last = make(map[PodRef]ContanersMetricsPoint)
				storage.prev = make(map[PodRef]ContanersMetricsPoint)
			},
			podRef:        NewPodRef("default", "pod1"),
			containerName: "container1",
			newPoint: MetricsPoint{
				StartTime:         now.Add(30 * time.Second), // 无效的开始时间
				Timestamp:         now,
				CumulativeCpuUsed: 1000,
			},
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.setup()
			newPrevPod := &ContanersMetricsPoint{Containers: make(map[string]MetricsPoint)}
			result := storage.handlePoint(tt.podRef, tt.containerName, tt.newPoint, newPrevPod)

			if result != tt.expected {
				t.Errorf("handlePoint() = %v, expected %v ", result, tt.expected)
			}
		})
	}
}
